#include "sysctl.h"
#include "dma.h"

/* start addresses for DSP text/data image and their length information
 * Their values are from flash image generation script of file 'gen_flash_image.py'
 */
#define DSP_IMAGE_START_ADDR      (0x30004400U)
#define DSP_IMAGE_INFO_START_ADDR (0x30004000U)
#define DMA_LENGTH                (256)

static inline void copy_trim()
{
    HWREG(TRIM_BASE + 0x24) = 0x55AA55AA; // unlock trim

    // copy trim data
    HWREG(TRIM_BASE + 0) = *((uint32_t *)(FLASH0OTP1_BASE + 0));
    HWREG(TRIM_BASE + 4) = *((uint32_t *)(FLASH0OTP1_BASE + 4));
    HWREG(TRIM_BASE + 8) = *((uint32_t *)(FLASH0OTP1_BASE + 8));

    HWREG(TRIM_BASE + 0x20) = 1;          // enable efuse
    HWREG(TRIM_BASE + 0x24) = 0xAA55AA55; // lock trim
}

static void dma_read(int src_addr, int dst_addr, int len)
{
    int tempSrc = src_addr;
    int tempDst = dst_addr;

    do
    {
        // 每次用DMA传输256字节
        DmaCh1Regs.SAR = tempSrc;
        DmaCh1Regs.DAR = tempDst;
        DmaRegs.CHEN   = 0x0101; // DMA CH1 启动

        while (!(DmaCh1Regs.INTSTATUS & 0x02)) // 等待结束中断标志位
            ;
        DmaCh1Regs.INTCLEAR = 0x02; // 清除结束中断标志位

        tempSrc = tempSrc + DMA_LENGTH;
        tempDst = tempDst + DMA_LENGTH;
        len     = len - DMA_LENGTH;

    } while (len > 0);
}

#ifdef __boot_core0__

static void Core0Boot_AcquireAllSRAM()
{
    DevCfgRegs.FETCH_SEL0  = 0x000000; // SRAM_N_BANK[23:00]: 0 - CPUx, 1 - CLAx
    DevCfgRegs.FARRAM_SEL0 = 0x00000;  // SRAM_F_BANK[19:00]: 0 - CPU0, 1 - CPU1
    DevCfgRegs.FARRAM_SEL1 = 0x00000;  // SRAM_F_BANK[39:20]: 0 - CPU0, 1 - CPU1
    DevCfgRegs.CORE_SEL    = 0x000000; // SRAM_N_BANK[23:00]: 0 - CPU0, 1 - CPU1
}

static void Core0Boot_ConfigSRAM()
{
    DevCfgRegs.FETCH_SEL0  = 0x000003; // SRAM_N_BANK[23:00]: 0 - CPUx, 1 - CLAx
    DevCfgRegs.CORE_SEL    = 0xF00000; // SRAM_N_BANK[23:00]: 0 - CPU0, 1 - CPU1
    DevCfgRegs.FARRAM_SEL0 = 0xFFFFF;  // SRAM_F_BANK[19:00]: 0 - CPU0, 1 - CPU1
    DevCfgRegs.FARRAM_SEL1 = 0xFFFFF;  // SRAM_F_BANK[39:20]: 0 - CPU0, 1 - CPU1
}

extern void Core0Boot_UpdateSP(Uint32 newSP);

#endif

extern u32 _CORE_CODE_ENTRY;

extern u32 _CORE0_DATA_START;
extern u32 _CORE1_DATA_START;
extern u32 _CORE0_INST_START;
extern u32 _CORE1_INST_START;

extern u32 _CORE0_CLA_DATA_START;
extern u32 _CORE0_CLA_INST_START;

extern u32 _BOOT_LOADER_SP_2;

extern void _dsp_boot_jmp_to_core(int);

// bit[17:15] of _CORE<n>_IRAM_START
#define CPU_EXP_START_MASK (0x3F)
#define CPU_EXP_START_OFST (15)

_Noreturn void qx_bootloader_main(void)
{
#define FLASH2DCSM_WORK_REG  (0x01007028)
#define FLASH2DCSM_WORK_MASK (0x1 << 4)
    // wait DCSM DMA transfer done
    // when DCSM_WAIT_REG, bit DCSM_WAIT_REG_MASK is 0. It is done.
    while (HWREG(FLASH2DCSM_WORK_REG) & FLASH2DCSM_WORK_MASK)
        ;

    // unlock dcsm
    HWREG(0x01079478) = 0;

    // copy trim data from flash OTP area to regfile
    // copy_trim();

#ifdef __boot_core0__
    CpuSysRegs.CORE_ADDR_EXP_START
        = ((int)(&_CORE0_INST_START) >> CPU_EXP_START_OFST) & CPU_EXP_START_MASK;
    CpuSysRegs.CLA_ADDR_EXP_START
        = ((int)(&_CORE0_CLA_INST_START) >> CPU_EXP_START_OFST) & CPU_EXP_START_MASK;
#else
    CpuSysRegs.CORE_ADDR_EXP_START
        = ((int)(&_CORE1_INST_START) >> CPU_EXP_START_OFST) & CPU_EXP_START_MASK;
#endif

#ifdef __boot_core0__
    NmiIntruptRegs.NMICFG.bit.NMIE = 1; // 使能全局NMI中断

    if (CpuTimer0Regs.TCR.bit.FREE == 0) // 借用TIMER.FREE默认值来区分前仿和真实的芯片环境
    {
        Core0Boot_AcquireAllSRAM();

        // 注意:
        // 1. ECC DMEM 校验和检错功能必须在boot启动最初阶段就完成配置
        // 2. ECC IMEM 校验功能必须主程序里开启, boot中启动会导致启动卡死
        // 3. ECC DMEM 纠错功能开启后将只支持全域32位内存操作, 需要在软件保证没有对DMEM的8/16位操作
        // EccRegs.ECC_IMEM_MODE.bit.ECC_FLASH_EN = 1; // 使能FLASH ECC检错功能
        // EccRegs.ECC_DMEM_MODE.bit.CHECK_MODE = 1; // 1: DATA MEM检错, 2: DATA MEM纠错

        CpuSysRegs.PCLKCR0.bit.DMA = 1; // 使能DMA时钟
        // 配置DMA固定参数
        DmaRegs.CFG.all                = 0x03; // DMA 使能, 中断使能
        DmaCh1Regs.BLOCK_TS            = 0x3f; // DMA block为256字节
        DmaCh1Regs.CFG_H.all           = 0x00; // DMA MEM to MEM
        DmaCh1Regs.CTL_L.bit.SRC_WIDTH = 2;    // SRC 位宽为32Bits
        DmaCh1Regs.CTL_L.bit.DST_WIDTH = 2;    // DST 位宽为32 Bits
        DmaCh1Regs.INTSIGNAL_EN        = 0x02; // DMA中断只保留DMA传输结束中断

        int *p = (int *)(DSP_IMAGE_INFO_START_ADDR);

        int core0_text_len = *(p + 0);
        int core0_data_len = *(p + 1);
        int core1_text_len = *(p + 2);
        int core1_data_len = *(p + 3);

        int cla0_text_len = *(p + 4);
        int cla0_data_len = *(p + 5);

        int flashCore0Text = DSP_IMAGE_START_ADDR;
        int flashCore0Data = flashCore0Text + core0_text_len;
        int flashCore1Text = flashCore0Data + core0_data_len;
        int flashCore1Data = flashCore1Text + core1_text_len;

        int flashCla0Text = flashCore1Data + core1_data_len;
        int flashCla0Data = flashCla0Text + cla0_text_len;

        // 从FLASH搬运数据到SARM
        // 先搬运DATA_RAM以避免flash ecc出错时nmi_isr函数中无法访问数据段数据
        if (core0_data_len != 0)
            dma_read(flashCore0Data, (int)(&_CORE0_DATA_START), core0_data_len);

        if (core0_text_len != 0)
            dma_read(flashCore0Text, (int)(&_CORE0_INST_START), core0_text_len);

        if (core1_data_len != 0)
            dma_read(flashCore1Data, (int)(&_CORE1_DATA_START), core1_data_len);

        if (core1_text_len != 0)
            dma_read(flashCore1Text, (int)(&_CORE1_INST_START), core1_text_len);

        if (cla0_data_len != 0)
            dma_read(flashCla0Data, (int)(&_CORE0_CLA_DATA_START), cla0_data_len);

        if (cla0_text_len != 0)
            dma_read(flashCla0Text, (int)(&_CORE0_CLA_INST_START), cla0_text_len);

        DmaRegs.RESET = 1;    // 复位DMA
        while (DmaRegs.RESET) // 等待复位
            ;
        CpuSysRegs.PCLKCR0.bit.DMA = 0; // 关闭DMA时钟
    }

    //
    // before jumping to main(),
    // 1. configure SRAM according to configuration in ldscript_Memory.ld
    // 2. update SP(GR30) to point SRAM owened by Core0
    // 3. wakeup Core1
    //
    Core0Boot_ConfigSRAM();

    Core0Boot_UpdateSP((Uint32)(&_BOOT_LOADER_SP_2));

    // enable instruction prefetch before jumping to DSP core
    HWREG(0x010720DC) = 0x1;
#endif

    // jump to DSP core and execute its first instruction @_CORE_CODE_ENTRY
    // @_CORE_CODE_ENTRY is defined in link scripts and is configurable
    _dsp_boot_jmp_to_core((int)(&_CORE_CODE_ENTRY));

    // add infinite loop to make compiler happy on _Noreturn
    while (1)
        ;
}
